use surrealdb::sql::Thing;

use super::kline::Kline;
use super::up2int;

/// Represents a detected "V" pattern in a series of financial `Kline`s.
#[derive(Debug)]
pub struct PatternV {
    /// The `Kline` representing the left maximum before the lowest point.
    pub left_max: Thing,
    /// The `Kline` at the lowest point of the "V" pattern.
    pub lowest: Thing,
    /// The `Kline` representing the right maximum after the lowest point.
    pub right_max: Thing,
    /// The price difference between the left maximum and the lowest point.
    pub left_high: f64,
    /// The price difference between the right maximum and the lowest point.
    pub right_high: f64,
}

/// Identifies and returns a `PatternV` representing a "V" pattern within a given slice of `Kline`s.
///
/// # Arguments
/// * `klines` - A slice of `Kline` structs representing financial data points ordered by time.
/// * `total_window_size` - The total window size in terms of the number of `Kline`s to consider around the lowest point for finding the "V" pattern.
///
/// # Returns
/// An `Option<PatternV>` encapsulating the detected "V" pattern with its components and height differences, or `None` if no pattern is found.
pub fn find_pattern_up_v(klines: &[Kline], total_window_size: usize) -> Option<PatternV> {
    let lowest_index = klines
        .iter()
        .enumerate()
        .min_by_key(|&(_, kline)| up2int(kline.low))
        .map(|(index, _)| index)
        .unwrap();

    let half_window_size = total_window_size / 2;

    let right_max_index = (lowest_index + 1..lowest_index + half_window_size + 1)
        .filter(|&i| i < klines.len())
        .max_by_key(|&i| up2int(klines[i].high))?;

    let right_to_lowest_span = right_max_index - lowest_index;

    let left_window_extra = if right_to_lowest_span < half_window_size {
        half_window_size - right_to_lowest_span
    } else {
        0
    };
    let left_window_size = half_window_size + left_window_extra;

    let left_max_index = (lowest_index.saturating_sub(left_window_size)..lowest_index)
        .rev()
        .max_by_key(|&i| up2int(klines[i].high) as i64)?;

    let left_high = klines[left_max_index].high - klines[lowest_index].low;
    let right_high = klines[right_max_index].high - klines[lowest_index].low;

    if let (Some(id1), Some(id2), Some(id3)) = (
        &klines[left_max_index].id,
        &klines[lowest_index].id,
        &klines[right_max_index].id,
    ) {
        return Some(PatternV {
            left_max: id1.clone(),
            lowest: id2.clone(),
            right_max: id3.clone(),
            left_high,
            right_high,
        });
    }

    None
}

// Import necessary crates or modules, assuming they are defined elsewhere in your project.
// For this example, no external imports are explicitly shown since the types like `Kline` are assumed to be defined in your context.

/// Represents a detected "downward V" pattern in a series of financial `Kline`s.
#[derive(Debug)]
pub struct PatternDownV {
    /// The `Kline` representing the left minimum before the toppest point.
    pub left_min: Thing,
    /// The `Kline` at the toppest point of the "downward V" pattern.
    pub toppest: Thing,
    /// The `Kline` representing the right minimum after the toppest point.
    pub right_min: Thing,
    /// The price difference between the high of the toppest point and the low of the left minimum.
    pub left_high: f64,
    /// The price difference between the high of the toppest point and the low of the right minimum.
    pub right_high: f64,
}

/// Identifies and returns a `PatternDownV` representing a "downward V" pattern within a given slice of `Kline`s.
///
/// # Arguments
/// * `klines` - A slice of `Kline` structs representing financial data points ordered by time.
/// * `total_window_size` - The total window size in terms of the number of `Kline`s to consider around the toppest point for finding the "downward V" pattern.
///
/// # Returns
/// An `Option<PatternDownV>` encapsulating the detected "downward V" pattern with its components and price differences, or `None` if no pattern is found.
pub fn find_pattern_down_v(klines: &[Kline], total_window_size: usize) -> Option<PatternDownV> {
    // Find the index of the Kline with the highest high price (toppest point).
    let toppest_index = klines
        .iter()
        .enumerate()
        .max_by_key(|&(_, kline)| up2int(kline.high))
        .map(|(index, _)| index)
        .unwrap();

    let half_window_size = total_window_size / 2;

    // Find the index of the Kline with the lowest low before the toppest point (left minimum).
    let left_min_index = (0..toppest_index.saturating_sub(half_window_size + 1))
        .rev()
        .min_by_key(|&i| up2int(klines[i].low))?;

    // Find the index of the Kline with the lowest low after the toppest point (right minimum).
    let right_min_index = (toppest_index + 1..toppest_index + half_window_size + 1)
        .filter(|&i| i < klines.len())
        .min_by_key(|&i| up2int(klines[i].low))?;

    // Calculate the price differences between the high of the toppest point and the lows of the left and right minima.
    let left_high = klines[toppest_index].high - klines[left_min_index].low;
    let right_high = klines[toppest_index].high - klines[right_min_index].low;

    if let (Some(id1), Some(id2), Some(id3)) = (
        &klines[left_min_index].id,
        &klines[toppest_index].id,
        &klines[right_min_index].id,
    ) {
        return Some(PatternDownV {
            left_min: id1.clone(),
            toppest: id2.clone(),
            right_min: id3.clone(),
            left_high,
            right_high,
        });
    }

    None
}

#[cfg(test)]
mod test {
    use super::{find_pattern_down_v, find_pattern_up_v};

    #[test]
    fn test_find_pattern_up_v() {
        // Usage example remains similar, but now handling the result as a `PatternV` struct.
        let klines = vec![
            // Populate with your Kline data here...
        ];
        let result = find_pattern_up_v(&klines, 20);
        if let Some(pattern_v) = result {
            println!("{:?}", pattern_v);
        }
    }

    #[test]
    fn test_find_pattern_down_v() {
        // Example usage: Assuming you have a vector of Kline objects named `data`.
        let klines = vec![
            // Populate with your Kline data here...
        ];
        let down_v_pattern = find_pattern_down_v(&klines, 20);
        match down_v_pattern {
            Some(pattern) => println!("Detected Downward V Pattern: {:?}", pattern),
            None => println!("No Downward V pattern detected."),
        }
    }
}
